﻿using System;
using System.Collections.Generic;
using System.Linq;
using Aptea.OpenXmlOffice;
using DocumentFormat.OpenXml.Spreadsheet;
using eCRAM.Model;
using eCRAM.Tools;

namespace eCRAM.Bll
{
    /// <summary>
    /// Genere le tableau contenant le détails du cram sur le mois
    /// </summary>
    public class ExcelSheetBodyGenerator
    {
        /// <summary>
        /// Cram contenant les infos à exporter
        /// </summary>
        public Cram Cram { get; set; }

        /// <summary>
        /// Nombre de projets affichables dans le mois
        /// </summary>
        public int NbProject { get; set; }
        /// <summary>
        /// Predicat de sélection des projets à afficher
        /// </summary>
        public Func<CramLine, bool> ProjectPredicate { get; set; }
        /// <summary>
        /// Predicat de sélection des congés à afficher
        /// </summary>
        public Func<CramLine, bool> HolidayPredicate { get; set; }
        /// <summary>
        /// Instance du manager excel
        /// </summary>
        public ExcelManager ExcelManager { get; set; }
        /// <summary>
        /// Index de la ligne en cours d'insertion dans l'onglet Excel
        /// </summary>
        public uint IdxRow { get; set; }
        /// <summary>
        /// Instance de l'onglet excel
        /// </summary>
        public Worksheet WorkSheet { get; set; }

        /// <summary>
        /// Index de la cellule correspondant au premier jour d'une ligne
        /// </summary>
        private uint IndexFirstDayCell
        {
            get { return 3; }
        }

        /// <summary>
        /// Index de la cellule contenant les totaux
        /// </summary>
        private uint IndexCellTotal
        {
            get { return IndexFirstDayCell + 31; }
        }

        /// <summary>
        /// Index de la cellule contenant les totaux
        /// </summary>
        private uint IndexCellVisa
        {
            get { return IndexCellTotal + 1; }
        }
        /// <summary>
        /// Nombre de jours travaillés dans le mois
        /// </summary>
        private int NbDaysInMonth
        {
            get { return DateTime.DaysInMonth(Cram.Year, Cram.Month); }
        }

        /// <summary>
        /// Genere le contenu du tableau
        /// </summary>
        /// <returns></returns>
        public uint Generate()
        {
            // Définition de la largeur des colonnes
            SetColumnsWidth();

            // Entête du Cram
            IdxRow = AddCramHeader();
            IdxRow++;

            // On calcule le total des jours travaillés
            var totalWorkedDays = AddProjectRows();

            // On calcule le total des jours de congés
            var totalVacationDays = AddHolidayRows();
            IdxRow++;

            // On écrit les totaux
            IdxRow = AddTotals(totalWorkedDays, totalVacationDays);
            IdxRow++;

            return IdxRow;
        }

        /// <summary>
        /// Définition de la largeur des colonnes
        /// </summary>
        private void SetColumnsWidth()
        {
            ExcelManager.AddColumnData(WorkSheet, 1, 1, 1);
            ExcelManager.AddColumnData(WorkSheet, 2, 2, 25);
            ExcelManager.AddColumnData(WorkSheet, 3, 33, 3);
            ExcelManager.AddColumnData(WorkSheet, 34, 34, 5);
            ExcelManager.AddColumnData(WorkSheet, 35, 35, 10);
        }

        /// <summary>
        /// Ajoute le header du tableau
        /// </summary>
        /// <returns></returns>
        private uint AddCramHeader()
        {
            // Nombre de jours dans le mois
            uint idxRowHeader = IdxRow + 1;
            var cellRef = ExcelManager.AddDataToSheet(WorkSheet, idxRowHeader, IndexFirstDayCell - 1, " Référence mission/projet");
            ExcelManager.AddStyleToCell(cellRef, CramExcelStyle.MonthStyle);

            for (uint index = 0; index < 31; index++)
            {
                var cell = ExcelManager.AddDataToSheet(WorkSheet, idxRowHeader, IndexFirstDayCell + index, index+1);
                ExcelManager.AddStyleToCell(cell, CramExcelStyle.DayBorderStyle);
            }


            var cellTotal = ExcelManager.AddDataToSheet(WorkSheet, idxRowHeader, IndexCellTotal, "Total");
            ExcelManager.AddStyleToCell(cellTotal, CramExcelStyle.DayBorderStyle);
            
            var cellVisa = ExcelManager.AddDataToSheet(WorkSheet, idxRowHeader, IndexCellVisa, "Visa");
            ExcelManager.AddStyleToCell(cellVisa, CramExcelStyle.GetDefaultCellSyleWithThickBorders(EnumBordure.All));

            return idxRowHeader;
        }

        /// <summary>
        /// Ajoute dans le tableau une ligne corespondant aux projets
        /// </summary>
        /// <returns></returns>
        private decimal AddProjectRows()
        {
            uint cptProject = 0;
            var totalWorkedDays = 0m;

            // On récupère les jours travaillés
            var workingDays = GetWorkingDays();

            if (Cram != null)
            {
                foreach (var line in Cram.CramLines.Where(ProjectPredicate).Take(NbProject))
                {
                    // on récupère la référence de mission
                    AddProjectInfo(line.Activity.Name, false);
                    var total = AddCramInfo(CramManager.GetMonthRates(Cram, line),
                        false,
                        CramExcelStyle.TableTotalStyle,
                        (cptProject + 1) == NbProject ? CramExcelStyle.LastVisaStyle : CramExcelStyle.VisaStyle); // Si derniere ligne des projets, on change le style de la cellule visa

                    totalWorkedDays += total;
                    IdxRow++;
                    cptProject++;
                }
            }


            while (cptProject < NbProject)
            {
                // on récupère la référence de missionDictionary<int, decimal> rates;
                AddProjectInfo("", false);
                AddEmptyRateInfo(workingDays, false,
                    CramExcelStyle.TableTotalStyle,
                    (cptProject + 1) == NbProject ? CramExcelStyle.LastVisaStyle : CramExcelStyle.VisaStyle);
                IdxRow++;
                cptProject++;
            }

            return totalWorkedDays;
        }

        /// <summary>
        /// Ajoute dans le tableau une ligne corespondant aux congés
        /// </summary>
        /// <returns></returns>
        private decimal AddHolidayRows()
        {
            var tabHolidays = ActivityManager.GetOrderedHolidaysForExcel();
            var totalVacationDays = 0m;

            // On récupère les jours travaillés
            var workingDays = GetWorkingDays();


            if (Cram != null)
            {
                foreach (var holiday in tabHolidays)
                {
                    var lastLine = holiday == tabHolidays[tabHolidays.Length - 1];
                    var totalStyle = lastLine ? CramExcelStyle.LastTableTotalStyle : CramExcelStyle.TableTotalStyle; // Si derniere ligne on modifie le style de la cellule
                    if (holiday == null)
                    {
                        AddProjectInfo("", lastLine);
                        AddEmptyRateInfo(workingDays, lastLine, totalStyle, null);
                    }
                    else
                    {
                        var line = Cram.CramLines.FirstOrDefault(l => l.Activity.Id == holiday.Id);
                        if (line != null)
                        {
                            AddProjectInfo(line.Activity.Name, lastLine);
                            totalVacationDays += AddCramInfo(
                                CramManager.GetMonthRates(Cram, line),
                                lastLine,
                                totalStyle,
                                null);
                        }
                        else
                        {
                            AddProjectInfo(holiday.Name, lastLine);
                            AddEmptyRateInfo(workingDays, lastLine, totalStyle, null);
                        }
                    }
                    IdxRow++;
                }
            }

            return totalVacationDays;
        }

        /// <summary>
        /// Ajoute le détail d'une ligne (projet ou congés)
        /// </summary>
        /// <param name="rates"></param>
        /// <param name="lastLine"></param>
        /// <param name="totalCellStyle"></param>
        /// <param name="visaCellStyle"></param>
        /// <returns></returns>
        private decimal AddCramInfo(Dictionary<int, decimal> rates, bool lastLine, ExcelCellStyle totalCellStyle, ExcelCellStyle visaCellStyle)
        {
            // On récupère les jours travaillés
            var workingDays = GetWorkingDays();

            for (var i = 0; i < 31; i++)
            {
                var toWrite = "";
                var style = CramExcelStyle.DayStyle;
                // Si le jour n'est pas présent dans le mois
                if (i >= NbDaysInMonth)
                {
                    style = CramExcelStyle.DisabledDay;
                }
                // si c'est un jour travaillé
                else if (/*workingDays[i] &&*/ rates[i + 1] != 0)
                {
                    toWrite = rates[i + 1].ToString("0.#");
                }
                
                // Si c'est un jour ferié
                if (workingDays.Count > i && !workingDays[i])
                {
                    style = CramExcelStyle.HolidayStyle;
                }

                if(lastLine)
                {
                    ConvertToLastLineStyle(style);
                }

                // on ajoute l'imputation
                var cell = ExcelManager.AddDataToSheet(WorkSheet, IdxRow, (uint)(IndexFirstDayCell + i), toWrite);
                ExcelManager.AddStyleToCell(cell, style);
            }

            var total = rates.Values.Sum();

            // Cellule contenant le total
            AddCellTableTotal(total, totalCellStyle);

            // Cellule contenant le visa
            if (visaCellStyle != null)
            {
                AddCellVisa(visaCellStyle);
            }

            return total;
        }

        /// <summary>
        /// Modifie un style pour lui ajouter une bordure en base de cellule
        /// </summary>
        /// <param name="style"></param>
        private void ConvertToLastLineStyle(ExcelCellStyle style)
        {
            style.Borders |= EnumBordure.Bottom;
            style.BottomBorderStyle = new BorderStyle{Color = CramExcelStyle.BorderColor, Style = CramExcelStyle.ThickBorderValue};
        }

        /// <summary>
        /// Ajoute une ligne correspondant à un projet (ou congés) sans imputations pour le mois
        /// </summary>
        /// <param name="workingDays"></param>
        /// <param name="LastLine"></param>
        /// <param name="totalCellStyle"></param>
        /// <param name="visaCellStyle"></param>
        private void AddEmptyRateInfo(IList<bool> workingDays, bool LastLine, ExcelCellStyle totalCellStyle, ExcelCellStyle visaCellStyle)
        {

            for (var i = 0; i < 31; i++)
            {
                var style = CramExcelStyle.DayStyle;
                if (i >= NbDaysInMonth)
                {
                    style = CramExcelStyle.DisabledDay;
                }
                else if (!workingDays[i])
                {
                    style = CramExcelStyle.HolidayStyle;
                }
                if(LastLine)
                {
                    ConvertToLastLineStyle(style);
                }
                var cell = ExcelManager.AddDataToSheet(WorkSheet, IdxRow, (uint)(IndexFirstDayCell + i), "");
                ExcelManager.AddStyleToCell(cell, style);

            }


            // Cellule contenant le total
            AddCellTableTotal(0, totalCellStyle);

            if (visaCellStyle != null)
            {
                // Cellule contenant le visa
                AddCellVisa(visaCellStyle);
            }
        }

        /// <summary>
        /// Ajout de la cellule total en bout de chaque ligne
        /// </summary>
        /// <param name="total"></param>
        /// <param name="cellStyle"></param>
        private void AddCellTableTotal(decimal total, ExcelCellStyle cellStyle)
        {
            var cellTotal = ExcelManager.AddDataToSheet(WorkSheet, IdxRow, IndexCellTotal, total == 0 ? "" : total.ToString("0.#"));
            ExcelManager.AddStyleToCell(cellTotal, cellStyle);
        }

        /// <summary>
        /// Ajout de la cellule visa en bout de chaque ligne de projet
        /// </summary>
        /// <param name="cellStyle"></param>
        private void AddCellVisa(ExcelCellStyle cellStyle)
        {

            var cellTotal = ExcelManager.AddDataToSheet(WorkSheet, IdxRow, IndexCellVisa, "");
            ExcelManager.AddStyleToCell(cellTotal, cellStyle);
        }

        /// <summary>
        /// Ajoute l'entete de la ligne correspondant au projet
        /// </summary>
        /// <param name="projectName"></param>
        /// <param name="lastLine"></param>
        private void AddProjectInfo(string projectName, bool lastLine)
        {
            var style = CramExcelStyle.ReferenceBorderStyle;
            if(lastLine) ConvertToLastLineStyle(style);
            // On ajoute la reférence
            var cell = ExcelManager.AddDataToSheet(WorkSheet, IdxRow, 2, " " + projectName);
            ExcelManager.AddStyleToCell(cell, style);
        }

        

        /// <summary>
        /// Ajoute les totaux des jours pour le mois
        /// </summary>
        /// <param name="totalWorkedDays"></param>
        /// <param name="totalVacationDays"></param>
        /// <returns></returns>
        private uint AddTotals(decimal totalWorkedDays, decimal totalVacationDays)
        {
            const uint startColumnIndex = 27;
            const uint endColumnIndex = 33;
            const uint largeurCommentaire = 18;

            // Label Commentaire
            WorkSheet.GetMergeCells(IdxRow, IndexFirstDayCell, IdxRow, IndexFirstDayCell + largeurCommentaire);
            ExcelManager.AddDataToSheet(WorkSheet, IdxRow, IndexFirstDayCell, "Commentaires");
            ExcelManager.AddStyleToCells(WorkSheet, IdxRow, IndexFirstDayCell, IdxRow, IndexFirstDayCell + largeurCommentaire, 
                CramExcelStyle.GetDefaultCellSyleWithThickBorders(EnumBordure.Left | EnumBordure.Right | EnumBordure.Top));

            
            // Jours travaillés
            WorkSheet.GetMergeCells(IdxRow, startColumnIndex, IdxRow, endColumnIndex);
            ExcelManager.AddDataToSheet(WorkSheet, IdxRow, startColumnIndex, " Jours travaillés ");
            ExcelManager.AddStyleToCells(WorkSheet, IdxRow, startColumnIndex, IdxRow, endColumnIndex, 
                CramExcelStyle.GetDefaultCellSyleWithThickBorders(EnumBordure.Left | EnumBordure.Top));
            var cell = ExcelManager.AddDataToSheet(WorkSheet, IdxRow, endColumnIndex + 1, totalWorkedDays.ToString("0.#"));
            ExcelManager.AddStyleToCell(cell, CramExcelStyle.GetDefaultCellSyleWithThickBorders(EnumBordure.Right | EnumBordure.Top));
            IdxRow++;

            // Commmentaire
            WorkSheet.GetMergeCells(IdxRow , IndexFirstDayCell, IdxRow + 1, IndexFirstDayCell + largeurCommentaire);
            if (!string.IsNullOrEmpty(Cram.Comment))
            {
                ExcelManager.AddDataToSheet(WorkSheet, IdxRow, IndexFirstDayCell, Cram.Comment);
            }
            ExcelManager.AddStyleToCells(WorkSheet, IdxRow, IndexFirstDayCell, IdxRow + 1, IndexFirstDayCell + largeurCommentaire,
                CramExcelStyle.GetDefaultCellSyleWithThickBorders(EnumBordure.Left | EnumBordure.Right | EnumBordure.Bottom));

            // Jours d'absence
            WorkSheet.GetMergeCells(IdxRow, startColumnIndex, IdxRow, endColumnIndex);
            ExcelManager.AddDataToSheet(WorkSheet, IdxRow, startColumnIndex, " Jours d'absence ");
            ExcelManager.AddStyleToCells(WorkSheet, IdxRow, startColumnIndex, IdxRow, endColumnIndex, 
                CramExcelStyle.GetDefaultCellSyleWithThickBorders(EnumBordure.Left));
            cell = ExcelManager.AddDataToSheet(WorkSheet, IdxRow, endColumnIndex + 1, totalVacationDays.ToString("0.#"));
            ExcelManager.AddStyleToCell(cell, CramExcelStyle.GetDefaultCellSyleWithThickBorders(EnumBordure.Right ));
            IdxRow++;

            // Jours ouvres
            var nbWorkingDays = WorkingDay.MonthWorkingDays(Cram.Year, Cram.Month).Count(b => b);
            WorkSheet.GetMergeCells(IdxRow, startColumnIndex, IdxRow, endColumnIndex);
            ExcelManager.AddDataToSheet(WorkSheet, IdxRow, startColumnIndex, " Jours ouvrés ");
            ExcelManager.AddStyleToCells(WorkSheet, IdxRow, startColumnIndex, IdxRow, endColumnIndex, 
                CramExcelStyle.GetDefaultCellSyleWithThickBorders(EnumBordure.Bottom | EnumBordure.Left));
            cell = ExcelManager.AddDataToSheet(WorkSheet, IdxRow, endColumnIndex + 1, nbWorkingDays.ToString("0.#"));
            ExcelManager.AddStyleToCell(cell,
                 CramExcelStyle.GetDefaultCellSyleWithThickBorders(EnumBordure.Bottom | EnumBordure.Right));

            return IdxRow;
        }

        /// <summary>
        /// Obtient la liste des jours travaillés pour le cram
        /// </summary>
        /// <returns></returns>
        private List<bool> GetWorkingDays()
        {
            return WorkingDay.MonthWorkingDays(Cram.Year, Cram.Month);
        }

    }
}
